package com.fmdj.mps.service.impl;


import cn.hutool.core.convert.Convert;
import cn.hutool.core.map.MapUtil;
import com.fmdj.mps.service.DriverPositionService;

import com.fmdj.mps.util.CoordinateTransform;
import org.gavaghan.geodesy.Ellipsoid;
import org.gavaghan.geodesy.GeodeticCalculator;
import org.gavaghan.geodesy.GeodeticCurve;
import org.gavaghan.geodesy.GlobalCoordinates;
import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.data.geo.*;
import org.springframework.data.geo.Point;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;


import java.awt.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Service
public class DriverPositionServiceImpl implements DriverPositionService {

    @Autowired
    private RedisTemplate redisTemplate;

    //更新司机的位置供下单时乘客端查询到符合发送订单的条件
    @Override
    public void updatePositionCache(Map map) {
        //获取前端传来的参数
        Long driverId = MapUtil.getLong(map, "driverId");
        String latitude = MapUtil.getStr(map, "latitude");
        String longitude = MapUtil.getStr(map, "longitude");
        Integer rangeDistance = MapUtil.getInt(map, "rangeDistance");
        Integer orderDistance = MapUtil.getInt(map, "orderDistance");

        //创建point对象存储经纬度
        Point point = new Point(Convert.toDouble(longitude), Convert.toDouble(latitude));
        //将司机位置放到redis中
        redisTemplate.opsForGeo().add("driver_location", point, String.valueOf(driverId));

        //取出定向经纬度 没有则设置空
        String orientateLatitude = MapUtil.getStr(map, "orientateLatitude",null);
        String orientateLongitude = MapUtil.getStr(map, "orientateLongitude", null);

        //构建定向经纬度
        String orientation = (orientateLatitude != null && orientateLongitude != null) ? orientateLatitude + "," + orientateLongitude : "none";

        //将司机状态放到redis中并设置过期时间为60秒
        String driverStatus = String.format("%d#%d#%s", rangeDistance, orderDistance, orientation);
        redisTemplate.opsForValue().set("driver_online#"+driverId,driverStatus,60, TimeUnit.SECONDS);
    }

    @Override
    public void removePositionCache(long driverId) {

    }

    @Override
    public ArrayList selectBefittingDriverAboutOrder(double startPlaceLatitude, double startPlaceLongitude, double endPlaceLatitude, double endPlaceLongitude, double mileage) {

        // 创建一个以订单起始点为中心，半径为5公里的搜索区域
        Point point = new Point(startPlaceLongitude, startPlaceLatitude);
        Metric metric = RedisGeoCommands.DistanceUnit.KILOMETERS;
        Distance distance = new Distance(5, metric);
        Circle circle = new Circle(point, distance);

        // 创建GEO参数，包含距离、坐标信息，并按升序排列结果
        RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs()
                .includeDistance() //司机距离订单的距离
                .includeCoordinates() //司机的位置
                .sortAscending();//查找到的司机按照升序
        // 在Redis中查询司机位置信息
        GeoResults<RedisGeoCommands.GeoLocation<String>> radius = redisTemplate.opsForGeo().radius("driver_location", circle, args);

        // 创建一个存放符合条件司机的列表
        List<HashMap> list = new ArrayList<>();

        // 如果查询结果不为空，开始遍历符合条件的司机
        if (radius!=null) {
            for (GeoResult<RedisGeoCommands.GeoLocation<String>> result : radius) {
                RedisGeoCommands.GeoLocation<String> content = result.getContent();// 获取司机的位置信息
                String driverId = content.getName();// 获取司机ID
                double dist = result.getDistance().getValue();// 获取司机与订单起点的距离

                // 从Redis中获取司机是否在线的状态信息
                Object obj = redisTemplate.opsForValue().get("driver_online#" + driverId);
                if (obj==null) {
                    continue;// 如果司机不在线，跳过该司机
                }

                // 将在线状态信息进行分割，分别获取司机的接单范围、订单里程限制和定向位置
                String[] temp = obj.toString().split("#");
                int rangeDistance = Integer.parseInt(temp[0]); //司机接单范围
                int orderDistance = Integer.parseInt(temp[1]); //司机接单里程限制
                String orientation = temp[2]; //司机定向位置(可能会有)

                // 判断司机是否在其接单范围内，及订单里程是否符合司机的接单要求
                if (dist>rangeDistance || !isWithinOrderDistance(orderDistance,mileage)) {
                    continue; // 不符合条件，跳过该司机
                }

                // 判断司机的定向条件是否符合，若定向为"none"，则不限制，否则需要进一步判断
                Boolean bool = orientation.equals("none") || isWithinOrientation(
                        Double.parseDouble(orientation.split(",")[0]), //定向位置纬度
                        Double.parseDouble(orientation.split(",")[1]), //定向位置经度
                        endPlaceLatitude, endPlaceLongitude); //订单终点经纬度

                // 如果所有条件都符合，将司机信息加入结果集
                if (bool) {
                    // 创建一个Map来保存司机的ID和距离，并添加到列表中
                    list.add(new HashMap(Map.of("driverId",driverId,"distance",dist)));
                }
            }
        }

        return new ArrayList(list);
    }


    /**
     * 检查订单终点是否在司机的定向位置范围内
     * @param orientationLat 定向位置的纬度
     * @param orientationLon 定向位置的经度
     * @param endLat 订单终点的纬度
     * @param endLon 订单终点的经度
     * @return 如果订单终点在定向位置3000米范围内，则返回true，否则返回false
     */
    private boolean isWithinOrientation(double orientationLat, double orientationLon,
                                        double endLat, double endLon) {
        // 将定向点的火星坐标转换为GPS坐标
        double[] orientationCoords = CoordinateTransform.transformGCJ02ToWGS84(orientationLon, orientationLat);
        // 将订单终点的火星坐标转换为GPS坐标
        double[] endCoords = CoordinateTransform.transformGCJ02ToWGS84(endLon, endLat);
        // 创建GlobalCoordinates对象，用于计算两点之间的距离
        GlobalCoordinates point1 = new GlobalCoordinates(orientationCoords[1], orientationCoords[0]);
        GlobalCoordinates point2 = new GlobalCoordinates(endCoords[1], endCoords[0]);

        // 使用GeodeticCalculator计算两个GPS坐标之间的距离
        GeodeticCurve geoCurve = new GeodeticCalculator().calculateGeodeticCurve(Ellipsoid.WGS84, point1, point2);
        // 判断订单终点是否在定向位置的3000米范围内
        return geoCurve.getEllipsoidalDistance() <= 3000;
    }

    private boolean isWithinOrderDistance(int orderDistance, double mileage) {
        return orderDistance==0 // 司机接单里程不限制
                ||(orderDistance==5 && mileage<=5 && mileage>=0) //  订单里程小于等于5
                ||(orderDistance==10 && mileage<=10 && mileage>=5)// 订单里程小于等于10
                ||(orderDistance==15 && mileage<=15 && mileage>=10)//  订单里程小于等于15
                ||(orderDistance==20 && mileage<=20 && mileage>=15);//  订单里程小于等于20
    }

    //更新订单定位缓存,设置过期时间为10分钟 司机端会不断调用这个接口从而更新订单的定位信息
    @Override
    public void updateOrderPositionCache(Map map) {
        Long orderId = MapUtil.getLong(map, "orderId");
        String latitude = MapUtil.getStr(map, "latitude");
        String longitude = MapUtil.getStr(map, "longitude");
        String location = latitude + "#" + longitude;
        redisTemplate.opsForValue().set("order_location#" + orderId, location, 10, TimeUnit.MINUTES);
    }


    //乘客端查询订单定位缓存
    @Override
    public HashMap searchOrderPositionCache(long orderId) {
        //拿到订单的定位信息
        Object obj =redisTemplate.opsForValue().get("order_location#" + orderId);
        if (obj!=null) {
            String[] temp = obj.toString().split("#");
            String latitude = temp[0];
            String longitude = temp[1];

            HashMap<Object, Object> map = new HashMap<>() {{
                put("latitude", latitude);
                put("longitude", longitude);
            }};

            return map;
        }
        return null;
    }
}
